#include "mesh.h"
#include "vortex/core/obj_parser.h"
#include "vortex/core/rng.h"

namespace vortex
{
	Mesh1::Mesh1(const std::string& filepath)
	{
		load_obj_file(filepath.c_str(), vp, vt, vn, num_vertices);
	}

	Mesh1::~Mesh1()
	{
		if (vp != nullptr) delete vp;
		if (vn != nullptr) delete vn;
		if (vt != nullptr) delete vt;
	}

	Mesh::Mesh()
	{
		glGenVertexArrays(1, &vao);
		glBindVertexArray(vao);
		glGenBuffers(1, &vbo);
		glGenBuffers(1, &ibo);
	}

	Mesh::~Mesh()
	{
		glDeleteBuffers(1, &vbo);
		glDeleteBuffers(1, &ibo);
		glDeleteVertexArrays(1, &vao);
	}

	void Mesh::Load(const std::string& filepath)
	{
		// load mesh from assimp.

	}

	void Mesh::SetData(const std::vector<MeshVertex>& vertices,
		const std::vector<uint16_t>& indices)
	{
		glBindVertexArray(vao);

		glBindBuffer(GL_ARRAY_BUFFER, vbo);
		glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(MeshVertex), vertices.data(), GL_STATIC_DRAW);
		glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(MeshVertex), (void*)offsetof(MeshVertex, pos));
		glEnableVertexAttribArray(0);
		glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(MeshVertex), (void*)offsetof(MeshVertex, normal));
		glEnableVertexAttribArray(1);
		glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(MeshVertex), (void*)offsetof(MeshVertex, uv));
		glEnableVertexAttribArray(2);

		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
		glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(uint16_t), indices.data(), GL_STATIC_DRAW);
		
		glBindVertexArray(0);

		num_indices = uint32_t(indices.size());
	}

	std::shared_ptr<Mesh> Mesh::box()
	{
		std::vector<MeshVertex> vertices;

		MeshVertex v;
		v.uv = glm::vec2(0.0f, 0.0f);
		v.normal = glm::vec3(0.1f, 0.2f, 0.7f);

		std::vector<glm::vec3> positions = {
			// front
			glm::vec3(-0.5f, 0.5f, -0.5f),
			glm::vec3(-0.5f, -0.5f, -0.5f),
			glm::vec3(0.5f, -0.5f, -0.5f),
			glm::vec3(0.5f, 0.5f, -0.5f),
			// back
			glm::vec3(0.5f, 0.5f, 0.5f),
			glm::vec3(0.5f, -0.5f, 0.5f),
			glm::vec3(-0.5f, -0.5f, 0.5f),
			glm::vec3(-0.5f, 0.5f, 0.5f),
			// left
			glm::vec3(-0.5f, 0.5f, 0.5f),
			glm::vec3(-0.5f, -0.5f, 0.5f),
			glm::vec3(-0.5f, -0.5f, -0.5f),
			glm::vec3(-0.5f, 0.5f, -0.5f),
			// right
			glm::vec3(0.5f, 0.5f, -0.5f),
			glm::vec3(0.5f, -0.5f, -0.5f),
			glm::vec3(0.5f, -0.5f, 0.5f),
			glm::vec3(0.5f, 0.5f, 0.5f),
			// top
			glm::vec3(-0.5f, 0.5f, 0.5f),
			glm::vec3(-0.5f, 0.5f, -0.5f),
			glm::vec3(0.5f, 0.5f, -0.5f),
			glm::vec3(0.5f, 0.5f, 0.5f),
			// bottom
			glm::vec3(0.5f, -0.5f, 0.5f),
			glm::vec3(0.5f, -0.5f, -0.5f),
			glm::vec3(-0.5f, -0.5f, -0.5f),
			glm::vec3(-0.5f, -0.5f, 0.5f),
		};

		for (auto& p : positions)
		{
			v.pos = p;
			vertices.push_back(v);
		}

		std::vector<uint16_t> indices = {
			0, 1, 2, 0, 2, 3,
			4, 5, 6, 4, 6, 7,
			8, 9, 10, 8, 10, 11,
			12, 13, 14, 12, 14, 15,
			16, 17, 18, 16, 18, 19,
			20, 21, 22, 20, 22, 23
		};

		std::shared_ptr<Mesh> m = std::make_shared<Mesh>();
		m->SetData(vertices, indices);
		return m;
	}

	std::shared_ptr<Mesh> Mesh::square()
	{
		std::vector<MeshVertex> vertices;
		std::vector<uint16_t> indices;

		MeshVertex v;
		v.uv = glm::vec2(0.0f, 0.0f);
		v.normal = glm::vec3(1.0f, 1.0f, 1.0f);
		v.pos = glm::vec3(-0.5f, 0.5f, 0.0f);
		vertices.push_back(v);
		v.pos = glm::vec3(-0.5f, -0.5f, 0.0f);
		vertices.push_back(v);
		v.pos = glm::vec3(0.5f, -0.5f, 0.0f);
		vertices.push_back(v);
		v.pos = glm::vec3(0.5f, 0.5f, 0.0f);
		vertices.push_back(v);
		uint16_t index_array[] = {0, 1, 2, 0, 2, 3};
		for (int i = 0; i < 6; ++i)
			indices.push_back(index_array[i]);

		std::shared_ptr<Mesh> m = std::make_shared<Mesh>();
		m->SetData(vertices, indices);
		return m;
	}

	std::shared_ptr<Mesh> Mesh::plane()
	{
		// create vertices and indices
		int half_grid_count = 10;
		float grid_size = 0.4f;
		float height = -0.6f;

		std::vector<MeshVertex> vertices;
		std::vector<uint16_t> indices;

		// set vertices
		for (int x = -half_grid_count; x <= half_grid_count; ++x)
		{
			for (int y = -half_grid_count; y <= half_grid_count; ++y)
			{
				int index_x = (x + half_grid_count);
				int index_y = (y + half_grid_count);
				int index = index_y * (2 * half_grid_count + 1) + index_x;
				MeshVertex v;
				float h = RNG::random(0.0f, 0.1f);
				v.pos.x = x * grid_size;
				v.pos.y = height + h;
				v.pos.z = y * grid_size;

				v.normal.x = 0.5f;
				v.normal.y = h;
				v.normal.z = 0.5f;
				v.uv = glm::vec2(0.0f);
				vertices.push_back(v);
			}
		}
		// set indices
		for (int x = 0; x < 2 * half_grid_count; ++x)
		{
			for (int y = 0; y < 2 * half_grid_count; ++y)
			{
				// 2 triangles
				int bl = y * (2 * half_grid_count + 1) + x;
				int br = bl + 1;
				int tl = bl + 2 * half_grid_count + 1;
				int tr = tl + 1;
				indices.push_back(bl);
				indices.push_back(br);
				indices.push_back(tr);
				indices.push_back(bl);
				indices.push_back(tr);
				indices.push_back(tl);
			}
		}

		std::shared_ptr<Mesh> m = std::make_shared<Mesh>();
		m->SetData(vertices, indices);
		return m;
	}
}
